package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
@Slf4j
public class CacheClient {

    private StringRedisTemplate stringRedisTemplate;

    private final ExecutorService CACHE_REBUILD_POOL = Executors.newFixedThreadPool(100);

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 任意对象序列化为json封装成String类型的数据存入redis中，并可设置ttl时间
     */
    public void set(String key, Object value, Long ttl, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(value),ttl,unit);
    }

    /**
     * 任意对象序列化为json封装成String类型的数据存入redis中，并设置逻辑过期时间
     */
    public void setWithLogicExpire(String key, Object value, Long ttl, TimeUnit unit) {
        RedisData data = new RedisData();
        data.setData(value);
        data.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(ttl)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(data));
    }

    /**
     * 根据指定的key查询缓存，并反序列化为指定类型，利用缓存空值的方式解决缓存穿透问题
     **/
    public <T, ID> T get(Class<T> type, String keyPrefix, ID id, Function<ID, T> dbFallback, Long ttl, TimeUnit unit) {
        String key = keyPrefix + id;
        //从缓存中查数据
        String redisCache = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(redisCache)) {
            return JSONUtil.toBean(redisCache, type);
        }
        //缓存命中空字符串  等价于 redisCache==""
        if (redisCache != null) {
            return null;
        }
        //不存在时查询数据库
        T result = dbFallback.apply(id);
        if (BeanUtil.isNotEmpty(result)) {
            this.set(key, result, ttl, unit);
            return result;
        }
        stringRedisTemplate.opsForValue().set(key, "", ttl, unit);
        return null;
    }

    /**
     * 根据指定的key查询缓存，并反序列化为指定类型，利用逻辑过期解决缓存穿透问题
     */
    public <T, ID> T getWithLogicExpire(Class<T> type, String keyPrefix, ID id, Function<ID, T> dbFallback, Long ttl, TimeUnit unit) {
        String key = keyPrefix + id;
        String redisCache = stringRedisTemplate.opsForValue().get(key);
        //查不到数据
        if (StrUtil.isBlank(redisCache)) {
            return null;
        }
        //命中
        RedisData data = JSONUtil.toBean(redisCache, RedisData.class);
        T t = JSONUtil.toBean(JSONUtil.toJsonStr(data.getData()), type);
        //未过期
        if (data.getExpireTime().isAfter(LocalDateTime.now())) {
            return  t;
        }

        String lockKey = RedisConstants.LOCK_SHOP_KEY +id;
        //获取锁
        if (lockup(lockKey)){
            //新起线程查询并存入redis
            CACHE_REBUILD_POOL.submit(()->{
                try {
                    T apply = dbFallback.apply(id);
                    setWithLogicExpire(key,apply,ttl,unit);
                }finally {
                    lockout(lockKey);
                }
            });
        }
        return t;
    }


    //加锁
        private boolean lockup(String key) {
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1");
        return BooleanUtil.isTrue(aBoolean);
    }

    //解锁
    private boolean lockout(String key) {
        Boolean delete = stringRedisTemplate.delete(key);
        return BooleanUtil.isTrue(delete);
    }


}
